﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using CEED.Utils;

namespace CEED_Mini
{
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct CompFooter
    {
        public uint bufferTopAndBottom;
        public uint originalBottom;
    }

    class BLZ
    {
        public static long GetDecompressedSize(Stream encstream, int CompressedSize)
        {
            if (CompressedSize >= Marshal.SizeOf(typeof(CompFooter)))
            {
                long startoffset = encstream.Position;
                encstream.Seek(CompressedSize - 8, SeekOrigin.Current);
                CompFooter cf = StructReader.ReadStruct<CompFooter>(encstream);
                encstream.Position = startoffset;
                return CompressedSize + cf.originalBottom;
            }
            else
                throw new Exception("File length too short.");
        }
        public static byte[] Decompress(Stream encstream, long CompressedSize, long DecompressSize)
        {
            bool bResult = true;
            byte[] Decompressed = new byte[DecompressSize];

            if (CompressedSize >= Marshal.SizeOf(typeof(CompFooter)))
            {
                long startoffset = encstream.Position;
                encstream.Seek(CompressedSize - 8, SeekOrigin.Current);
                CompFooter cf = StructReader.ReadStruct<CompFooter>(encstream);
                encstream.Position = startoffset;
                uint Top = cf.bufferTopAndBottom & 0xFFFFFF;
                uint Bottom = cf.bufferTopAndBottom >> 24 & 0xFF;
                if (Bottom >= Marshal.SizeOf(typeof(CompFooter)) && Bottom <= Marshal.SizeOf(typeof(CompFooter)) + 3 && Top >= Bottom && Top <= CompressedSize && DecompressSize >= CompressedSize + cf.originalBottom)
                {
                    DecompressSize = CompressedSize + cf.originalBottom;
                    encstream.Read(Decompressed, 0, (int)CompressedSize);
                    int DestinationOffset = (int)DecompressSize ;
                    int SourceOffset = (int)(CompressedSize - Bottom);
                    int EndOffset = (int)(CompressedSize - Top);
                    while (SourceOffset - EndOffset > 0)
                    {
                        byte uFlag = Decompressed[--SourceOffset];
                        for (int i = 0; i < 8; i++)
                        {
                            if ((uFlag << i & 0x80) == 0)
                            {
                                if (DestinationOffset - EndOffset < 1 || SourceOffset - EndOffset < 1)
                                {
                                    bResult = false;
                                    break;
                                }
                                Decompressed[--DestinationOffset] = Decompressed[--SourceOffset];
                            }
                            else
                            {
                                if (SourceOffset - EndOffset < 2)
                                {
                                    bResult = false;
                                    break;
                                }
                                int nSize = Decompressed[--SourceOffset];
                                int nOffset = (((nSize & 0x0F) << 8) | Decompressed[--SourceOffset]) + 3;
                                nSize = (nSize >> 4 & 0x0F) + 3;
                                if (nSize > DestinationOffset - EndOffset)
                                {
                                    bResult = false;
                                    break;
                                }
                                int pData = DestinationOffset + nOffset;
                                if (DestinationOffset > DecompressSize - 1)
                                {
                                    bResult = false;
                                    break;
                                }
                                for (int j = 0; j < nSize; j++)
                                {
                                    Decompressed[--DestinationOffset] = Decompressed[--pData];
                                }
                            }
                            if (SourceOffset - EndOffset <= 0)
                            {
                                break;
                            }
                        }
                        if(!bResult)
                        {
                            break;
                        }
                    }
                }
                else
                {
                    bResult = false;
                }
            }
            else
            {
                bResult = false;
            }
            if(!bResult)
            {
                throw new Exception("Wrong length");
            }

            return Decompressed;
        }
    }
}
